﻿using System;
using System.Runtime.Serialization;
using CI.Interfaces;

namespace CI.Organism
{
    /// <summary>Asexual Reproductive Gene class</summary>
    [DataContract]
    public class GeneReproductionAsexual : GeneReproduction, IGene
    {
        /// <summary>
        /// Asexual repoduction gene
        /// </summary>
        /// <param name="organism">childOrganism the gene belongs to</param>
        /// <param name="guid">guid for gene</param>
        public GeneReproductionAsexual(IOrganism organism, Guid guid) : base(organism, guid) { }

        /// <summary>
        /// Action the Reproduction of the Organism 
        /// </summary>
        public override void Action()
        {
            SimEnv.TraceLogger.LogDebug(string.Format("Actioning Gene : GeneReproductionAsexual on organism {0}", Organism.Guid));

            // make sure the parent Organism exists in the environment
            if (!(Organism.HostingEnvironment.HostingEnvironmentPhysical.Registered(Organism)))
            {
                SimEnv.TraceLogger.LogDebug("Unable action Asexual Gene as the parent does not exist in the physical environment yet");
                return;
            }

            // make sure it has enough energy, if not dont try and breed
            if (Organism.Energy <= Organism.InitEnergy)
                return;

            // create the offspring
            var childOrganism = new Organism(Organism.HostingEnvironment, Guid.NewGuid(), Organism.InitEnergy, Organism.InitCIET);

            // tell the child who's its parent was
            childOrganism.ParentOrganisms.Add(Organism);

            // if their is already a Organism with this guid create a new one(shouldnt happen)
            while (Organism.Organisms.Find(childOrganism.Guid) != null)
                childOrganism.Guid = Guid.NewGuid();

            SimEnv.TraceLogger.LogDebug(string.Format("New organism {0} created from {1} via Asexual reproduction", childOrganism.Guid, Organism.Guid));

            // copy the type of neural system its parent has and create the same one for itself if its has one
            if (Organism.NeuralSystem != null)
                childOrganism.NeuralSystem = (INeuralSystem)Organism.NeuralSystem.GetType().GetConstructor(new[] { Organism.GetType() }).Invoke(new object[] { childOrganism });

            // copy the cells collection, so we set the mutation rate to be zero
            // the cells themselves will get mutated via the dna/gene/cell mutation which happens a few more lines down
            childOrganism.Cells = Organism.Cells.Mutate(0, childOrganism);

            SimEnv.TraceLogger.LogDebug(string.Format("{0} Cells copied from parent {1} to child {2}", childOrganism.Cells.Count, Organism.Guid, childOrganism.Guid));

            // mutate the neural system
            if (childOrganism.NeuralSystem != null)
                childOrganism.NeuralSystem.Mutate(MutationRate, Organism);

            // copy the mutated DNA to the new Organism
            childOrganism.DNA = Organism.DNA.Mutate(MutationRate, childOrganism);

            childOrganism.DNA.UpdateMutatedCells(childOrganism.Cells);

            // set the child location
            childOrganism.Location = new Location(Organism.Location.XAxis, (Organism.Location.YAxis + (Organism.Location.YRadius * 2)), Organism.Location.ZAxis, Organism.Location.XRadius, Organism.Location.YRadius, Organism.Location.ZRadius);

            // add it to the collection
            childOrganism.Organisms = Organism.Organisms;

            // mark it as alive
            Organism.Organisms.AliveAdd(childOrganism);

            // give it the starting energy levels as this one has
            childOrganism.Energy = Organism.InitEnergy;

            // take it from parent childOrganism
            Organism.Energy -= Organism.InitEnergy;

            // set it as the same ciet
            childOrganism.CIET = Organism.CIET;

            // if we are in a simulation lets asses the organisms fitness before we create it in the environment
            // also check the simulation restrictions
            if (childOrganism.HostingEnvironment.Simulation != null)
            {
                // assess fitness
                childOrganism.HostingEnvironment.Simulation.FitnessChecking(childOrganism);
                // check simulation restrictions
                childOrganism.HostingEnvironment.Simulation.RestrictionChecking(Organism.HostingEnvironment);
            }

        }


        /// <summary>
        /// Take this parents Gene and create a new Gene to be put in the new child organism
        /// Mutate the current Gene and return a new one, deep class level mutation
        /// </summary>
        /// <param name="mutationRate">mutation rate to be applied</param>
        /// <param name="childOrganism">child Organism it will be going into</param>
        /// <returns>new gene for child</returns>
        public override IGene Mutate(double mutationRate, IOrganism childOrganism)
        {
            #region Argument check
            if (childOrganism == null)
                throw new ArgumentException("Organism");
            #endregion

            // create a new Reproduction Gene
            var childGene = new GeneReproductionAsexual(childOrganism, Guid);

            // mutate the requirements
            childGene.Requirements = Requirements.Mutate(mutationRate, childOrganism);

            // Alter Mutation rate
            childGene.MutationRate = Mutation.MutateCrawlOrLeap(mutationRate, mutationRate);

            return childGene;
        }

    }
}